use core::intrinsics::likely;
use core::{mem::MaybeUninit, sync::atomic::AtomicUsize};

use cpuindex::{nr_cpus, this_processor_id};
use raw_perdata::RawPerdata;

use crate::{
    page::Page,
    zone::{FreeArea, Zone, ZoneType, MAX_ORDER},
    GfpFlags,
};

struct GlobalData {
    zones: [Zone; ZoneType::Max as usize],
    cpu_num: usize,
    start_pfn: usize,
    free_pages: AtomicUsize,
    total_pages: AtomicUsize,
    reserved_pages: AtomicUsize,
}

static mut GLOBAL_PAGE_ALLOC: MaybeUninit<GlobalData> = MaybeUninit::uninit();

pub(crate) fn zone_idx(ty: ZoneType) -> &'static mut Zone {
    unsafe { &mut GLOBAL_PAGE_ALLOC.as_mut_ptr().as_mut().unwrap().zones[ty as usize] }
}

pub fn nr_reserved_pages() -> usize {
    unsafe {
        GLOBAL_PAGE_ALLOC
            .as_ptr()
            .as_ref()
            .unwrap()
            .reserved_pages
            .load(core::sync::atomic::Ordering::Relaxed)
    }
}

pub fn nr_total_pages() -> usize {
    unsafe {
        GLOBAL_PAGE_ALLOC
            .as_ptr()
            .as_ref()
            .unwrap()
            .total_pages
            .load(core::sync::atomic::Ordering::Relaxed)
    }
}

pub fn nr_free_pages() -> usize {
    unsafe {
        GLOBAL_PAGE_ALLOC
            .as_ptr()
            .as_ref()
            .unwrap()
            .free_pages
            .load(core::sync::atomic::Ordering::Relaxed)
    }
}

pub fn start_pfn() -> usize {
    unsafe { GLOBAL_PAGE_ALLOC.as_ptr().as_ref().unwrap().start_pfn }
}

pub(crate) fn get_page_from_freelist(
    gfp_mask: GfpFlags,
    order: usize,
    ty: ZoneType,
) -> Option<*mut Page> {
    let mut zone;

    unsafe {
        zone = &mut GLOBAL_PAGE_ALLOC.as_mut_ptr().as_mut().unwrap().zones[ty as usize];
    }

    let mut page;
    let mut train = 0;
    loop {
        unsafe {
            page = zone.rmqueue(order, gfp_mask);

            if let Some(p) = page {
                p.as_mut().unwrap().prep_new_page(order, gfp_mask);
                return page;
            }
        }

        for cpu in 0..nr_cpus() {
            if cpu == this_processor_id() {
                continue;
            }
            train += zone.drain_pages_zone(cpu);
        }
        if likely(train != 0) {
            continue;
        }
        return None;
    }
}
